﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using IWMAG.Scripting.AST;
using IWMAG.Scripting.AST.Declarations;
using IWMAG.Scripting.AST.ObjectModel;
using IWMAG.Scripting.AST.Statements;
using IWMAG.Scripting.Compiler;
using IWMAG.Scripting.Helpers;
using IWMAG.Scripting.Types;
using System.Text;

namespace IWMAG.Scripting.Interpreter {
    public class ScriptEngine {
        const int MaxStackCount = 1024;

        private string _Code;
        public string Code {
            get {
                return _Code;
            }
            set {
                _Code = value;
                Compiled = false;
                Program = null;
                Globals = null;
                CompiledTable = null;
                Pool.Clear();
                NewPool.Clear();
            }
        }

        public bool IsExterned { get; internal set; }
        public TextWriter Writer { get; set;}

        internal RuntimeTable Globals;
        internal CompileTable CompiledTable;
        internal bool KillExecution = false;

        public bool Compiled { get; private set; }

        private int _StackCount = 0;
        public int StackCount {
            get { return _StackCount; }
            set {
                _StackCount = value;
                if (StackCount >= MaxStackCount)
                {
                    throw new StackOverflowException();
                }
            }
        }

        public ScriptTask ExecutingThread { get; protected set; }
        public List<Assembly> ExposedAssemblies = new List<Assembly>();

        protected int LastEval = 0;        
        protected bool FirstRun = true;
        protected ProgramDeclaration Program;
        protected List<ScriptTask> Pool = new List<ScriptTask>();
        protected List<ScriptTask> NewPool = new List<ScriptTask>();
        protected List<Tuple<VariableDeclaration, object>> Introductions = new List<Tuple<VariableDeclaration, object>>();
        protected List<ExternDeclaration> Externalizations = new List<ExternDeclaration>();
        protected List<ImportStatement> Imports = new List<ImportStatement>();

        public bool HasOpenContexts {
            get {
                return Pool.Count + NewPool.Count != 0;
            }
        }

        public ScriptEngine(Assembly asm = null, string code = "") {
            Code = code;

            ExposedAssemblies.Add(Assembly.GetExecutingAssembly());
            if (asm != null) {
                ExposedAssemblies.Add(asm);
            }

            Introduce("time", 0.0);
            Writer = Console.Out;
        }

        public IUserInfo[] Compile(string s = null) {
            if (s != null) {
                Code = s;
            }

            Parser p = null;
            try {
                p = new Parser();
                Program = p.Parse(Code);
            } catch (ParseException) {
            }

            if (p.ErrorList.Where(e=>!(e.CanCompleteBuild.HasValue && e.CanCompleteBuild.Value)).Count() != 0) {
                return p.ErrorList.ToArray();
            }

            CompiledTable = Program;
            CompiledTable.Engine = this;

            foreach (ImportStatement imp in Imports) {
                imp.Initialize(this, p.ErrorList);
                Program.Imports.Add(imp);
                CompiledTable.Imports[imp.ClrName as ReferenceType] = imp.ClrType;
                CompiledTable.Aliases[imp.Alias as ReferenceType] = imp.ClrName;
            }

            foreach (Tuple<VariableDeclaration, object> preset in Introductions) {
                // this line might not be necessary
                Program.Variables.Add(preset.Item1);
                CompiledTable.Variables[preset.Item1.Name] = preset.Item1.Type;
            }

            foreach (ExternDeclaration ext in Externalizations) {
                Program.Externs.Add(ext);
                CompiledTable.Functions[ext.Signature.Name] = ext.Signature;
            }

            try {
                Program.Verify(CompiledTable, p.ErrorList);
            } catch (VerifyException) {
            }

            if (p.ErrorList.Where(e=>!(e.CanCompleteBuild.HasValue && e.CanCompleteBuild.Value)).Count() != 0) {
                return p.ErrorList.ToArray();
            }

            Globals = Program;
            Globals.Engine = this;

            foreach (ExternDeclaration ext in Externalizations) {
                Globals.Externs[ext.Signature.Name] = ext;
            }

            foreach (VariableDeclaration v in Program.Variables) {
                if (v.InitialValue != null) {
                    Reference<RunValue> val = RunValue.NoValue;
                    foreach (Continuation wait in v.InitialValue.ValueOf(this, Globals, val)) ;

                    Globals.Variables[v.Name] = val.ReferencedValue;
                } else {
                    Globals.Variables[v.Name] = RunValue.NoValue;
                }
            }

            foreach (Tuple<VariableDeclaration, object> preset in Introductions) {
                if (preset.Item1.Type is ArrayType) {
                    Globals.Variables[preset.Item1.Name].Value = new ArrayRunValue(preset.Item2 as IEnumerable);
                } else {
                    Globals.Variables[preset.Item1.Name] = new RunValue(preset.Item2);
                }
            }

            Compiled = true;

            return p.ErrorList.ToArray();
        }

        /// <summary>
        /// Performs a function call and returns the result.
        /// </summary>
        /// <param name="name">The name of the function to call. This function will return immediately - wait statements will have no effect.</param>
        /// <param name="args"></param>
        /// <returns></returns>
        public object Call(string name, params RunValue[] args) {
            RuntimeTable table = new RuntimeTable(Program);
            table.Parent = Globals;

            FunctionDeclaration func = Program.Functions.Where((a) => a.Signature.Name == name).SingleOrDefault();
            if (func == null) {
                Writer.WriteLine("Unable to call function `" + name + "`");
                return 0;
            }

            Reference<RunValue> val = RunValue.NoValue;
            foreach (Continuation wait in func.ValueOf(this, table, val, args));

            return val.ReferencedValue.Value;
        }

        public int Start(string name, params RunValue[] args) {
            RuntimeTable table = new RuntimeTable(Program);
            table.Parent = Globals;

            FunctionDeclaration func = Program.Functions.Where((a) => a.Signature.Name == name).SingleOrDefault();
            if (func == null) {
                Writer.WriteLine("Unable to call function `" + name + "`");
                return 0;
            }

            IEnumerator<Continuation> p = func.ValueOf(this, table, RunValue.NoValue, args).GetEnumerator();
            NewPool.Add(new ScriptTask(-1337, p, name));

            return p.GetHashCode();
        }

        protected Continuation Resume(IEnumerator<Continuation> a, bool first = false) {
            if (!first) {
                if (!a.MoveNext()) {
                    return new Continuation(Continuation.Token.Finalized);
                }
            }

            return a.Current;
        }

        public void Update(float duration) {
            if (!Compiled) {
                return;
            }

            if (!FirstRun) {
                SetValue("time", (double)GetValue("time") + duration);
            } else {
                FirstRun = false;
            }

            if (NewPool.Count != 0) {
                foreach (ScriptTask newtask in NewPool) {
                    Pool.Add(newtask);
                }
                NewPool.Clear();
            }

            List<ScriptTask> finished = new List<ScriptTask>();
            int j = 0;
            foreach (ScriptTask context in Pool) {
                KillExecution = false;
                ExecutingThread = context;
                bool first = context.Waiting == -1337;
                context.Waiting -= (int) (duration * 1000);
                if (context.Waiting <= 0) {
                    try {
                        Continuation c = Resume(context.Ptr, first);
                        context.Waiting = c.Data;
                        if (c.Command == Continuation.Token.Finalized || c.Command == Continuation.Token.Killed) {
                            finished.Add(context);
                        }
                    } catch (LatebindFailedException) {
                        Writer.WriteLine("Latebind expression failed in function `{0}`", ExecutingThread.Name);
                        finished.Add(context);
                    }
                }
                j++;
            }
            foreach (ScriptTask task in finished) {
                Pool.Remove(task);
            }
        }

        public void Kill() {
            Writer.WriteLine("Killing execution of function `{0}`", ExecutingThread.Name);
            KillExecution = true;
        }

        public void Stop() {
            Stop(ExecutingThread);
        }

        public void Stop(ScriptTask task) {
            Pool.Remove(task);
        }

        public void Introduce(string name, object value) {
            CompileTable table = new CompileTable();
            Lexeme l = default(Lexeme);
            CodeType type = Marshaller.Marshal(value.GetType(), table);
            Introductions.Add(Tuple.Create(new VariableDeclaration(l, type, name), value));
        }

        public void Externalize(string name, string method, object instance) {
            Externalize(name, instance.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.NonPublic), instance);
        }

        public void Externalize(string name, string method, Type type) {
            Externalize(name, type.GetMethod(method), null);
        }

        public void Externalize(string name, MethodBase method, object instance = null) {
            ExternDeclaration ext = new ExternDeclaration(default(Lexeme), instance, method);
            ext.Signature.Name = name;
            Externalizations.Add(ext);
        }

        public void MakeConstructor(string name, Type t) {
            ImportType(name, t);
            Externalize(name, t.GetConstructors().First());
        }

        public void ImportType(string alias, Type type) {
            Imports.Add(new ImportStatement(default(Lexeme), type, new ReferenceType(alias.Split('.'))));
        }

        public object GetValue(string name) {
            return Globals.Variables[name].Value;
        }

        public T GetValue<T>(string name) {
            return (T)Globals.Variables[name].Value;
        }

        public IEnumerable<object> GetArray(string name) {
            foreach (RunValue o in Globals.Variables[name].ArrayValue) {
                yield return o.Value;
            }
        }

        public IEnumerable<T> GetArray<T>(string name) {
            foreach (RunValue o in Globals.Variables[name].ArrayValue) {
                yield return (T)o.Value;
            }
        }

        public void SetValue(string name, object val) {
            Globals.Variables[name].Value = val;
        }
    }
}
